import {CancellationToken, HttpError, newError, UpdateInfo} from "builder-util-runtime"
import {GiteeOptions, giteeUrl} from "./publishOptions"
import * as semver from "semver"
import {URL} from "url"
import {AppUpdater, ResolvedUpdateFileInfo} from "electron-updater"
import {getChannelFilename, newBaseUrl, newUrlFromBase} from "electron-updater/out/util"
import {parseUpdateInfo, Provider, ProviderRuntimeOptions, resolveFiles} from "electron-updater/out/providers/Provider"

interface GiteeUpdateInfo extends UpdateInfo {
  tag: string
}

export abstract class BaseGiteeProvider<T extends UpdateInfo> extends Provider<T> {
  // so, we don't need to parse port (because node http doesn't support host as url does)
  protected readonly baseUrl: URL
  protected readonly baseApiUrl: URL

  protected constructor(
    protected readonly options: GiteeOptions,
    defaultHost: string,
    runtimeOptions: ProviderRuntimeOptions
  ) {
    super({
      ...runtimeOptions,
      /* because GitHib uses S3 */
      isUseMultipleRangeRequest: false,
    })

    this.baseUrl = newBaseUrl(giteeUrl(options, defaultHost))
    const apiHost = defaultHost === "gitee.com" ? "api.gitee.com" : defaultHost
    this.baseApiUrl = newBaseUrl(giteeUrl(options, apiHost))
  }
}

export class GiteeProvider extends BaseGiteeProvider<GiteeUpdateInfo> {
  constructor(
    protected readonly options: GiteeOptions,
    private readonly updater: AppUpdater,
    runtimeOptions: ProviderRuntimeOptions
  ) {
    super(options, "gitee.com", runtimeOptions)
  }

  private get channel(): string {
    const result = this.updater.channel || this.options.channel
    return result == null ? this.getDefaultChannelName() : this.getCustomChannelName(result)
  }

  async getLatestVersion(): Promise<GiteeUpdateInfo> {
    const cancellationToken = new CancellationToken()

    const feedXml: string = (await this.httpRequest(
      newUrlFromBase(`${this.basePath}/latest?access_token=${this.options.token}`, this.baseUrl),
      {
        accept: "application/xml, application/atom+xml, text/xml, */*",
      },
      cancellationToken
    ))!
    const res = JSON.parse(feedXml)
    let rawData: string
    let channelFile = ""
    let channelFileUrl: any = ""
    const fetchData = async (channelName: string) => {
      channelFile = getChannelFilename(channelName)
      channelFileUrl = new URL(res.assets?.find(x => x.name == channelFile).browser_download_url)
      const requestOptions = this.createRequestOptions(channelFileUrl)
      try {
        return (await this.executor.request(requestOptions, cancellationToken))!
      } catch (e: any) {
        if (e instanceof HttpError && e.statusCode === 404) {
          throw newError(`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND")
        }
        throw e
      }
    }

    try {
      let channel = this.channel
      rawData = await fetchData(channel)
    } catch (e: any) {
      if (this.updater.allowPrerelease) {
        // Allow fallback to `latest.yml`
        rawData = await fetchData(this.getDefaultChannelName())
      } else {
        throw e
      }
    }
    const result = parseUpdateInfo(rawData, channelFile, channelFileUrl)
    if (result.releaseName == null) {
      result.releaseName = res.name
    }

    if (result.releaseNotes == null && semver.lt(this.updater.currentVersion.version, result.version)) {
      result.releaseNotes = [{
        version: result.version,
        note: res.body,
      }]
    }
    return {
      tag: res.tag_name,
      ...result,
    }
  }

  private get basePath(): string {
    return `/api/v5/repos/${this.options.owner}/${this.options.repo}/releases`
  }

  resolveFiles(updateInfo: GiteeUpdateInfo): Array<ResolvedUpdateFileInfo> {
    // still replace space to - due to backward compatibility
    return resolveFiles(updateInfo, this.baseUrl, p => this.getBaseDownloadPath(updateInfo.tag, p.replace(/ /g, "-")))
  }

  private getBaseDownloadPath(tag: string, fileName: string): string {
    return `https://gitee.com/${this.options.owner}/${this.options.repo}/releases/download/${tag}/${fileName}`
  }
}